<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>象棋</title>
    <style>
        html,
        body,
        div,
        p {
            margin: 0;
            padding: 0;
            user-select: none;
        }

        #rooms {
            display: flex;
            flex-wrap: wrap;
        }

        #rooms>div {
            display: flex;
            align-items: center;
            border: 1px solid red;
            border-radius: 5px;
            padding: 10px;
            margin: 0 0 10px 10px;
        }

        #rooms label {
            padding: 5px 10px;
        }

        #rooms span {
            padding: 10px 20px;
            border: 1px solid #ccc;
            margin: 0 3px;
        }

        #canvasPanel {
            display: flex;
        }

        #canvasPanel>div:last-child {
            flex: 1;
        }

        h1,
        h2 {
            text-align: center;
        }

        #msgs {
            border: 1px solid #ccc;
            border-radius: 3px;
            overflow: auto;
        }
    </style>
</head>

<body>
    <!-- 登录面板 -->
    <div id="loginPanel">
        <p>
            链接：<input type="text" id="url" value="ws://47.104.71.104:8181" />
        </p>
        <p>
            昵称：<input type="text" id="name">
        </p>
        <p>
            <input type="button" value="登录" id="btnLogin" />
        </p>
    </div>
    <!-- 房间列表 -->
    <div id="rooms" style="display: none;">
    </div>
    <!-- 棋盘 -->
    <div id="canvasPanel" style="display:none">
        <div>
            <h1 id="title"></h1>
            <h2 id="opponent"></h2>
            <canvas id="canvas" width="300" height="600"></canvas>
            <h2 id="self"></h2>
        </div>
        <div>
            <p id="msgs"></p>
            <input type="button" value="退出" id="exit">
            <input type="button" value="悔棋" id="huiqi">
            <input type="button" value="投降" id="touxiang">
        </div>
    </div>
    <script>
        const visible = (dom, show = false) => {
            dom.style.display = show ? '' : 'none';
        }

        let ws, nickName, uid, roomList, isStart = false, drawing = false, waiting = false, room, trun = true;

        // 设置回合状态
        const setTrun = (value) => {
            trun = value;
            if (value) {
                addMsg('本次是你的回合，请走棋');
            } else {
                addMsg('本次是对方的回合，请等待对方走棋');
            }
        };

        const btnLogin = document.getElementById('btnLogin'),
            loginPanel = document.all.loginPanel,
            rooms = document.all.rooms,
            canvasPanel = document.all.canvasPanel,
            msgs = document.all.msgs,
            touxiang = document.all.touxiang,
            huiqi = document.all.huiqi,
            tuichu = document.all.exit,
            canvas = document.getElementById('canvas');

        // 创建房间
        const createRooms = (_rooms) => {
            roomList = _rooms;
            let html = '';
            for (const room of _rooms) {
                html += `<div rid="${room.id}">
            <label>${room.users[0]?.name ?? ""}</label>
            <span>${room.name}</span>
            <label>${room.users[1]?.name ?? ""}</label>
        </div>`;
            }
            rooms.innerHTML = html;
        }

        // 退出
        tuichu.onclick = () => {
            if (!confirm('您确定要退出游戏吗？')) {
                return;
            }
            send({ type: 'exit' });
            visible(canvasPanel);
            visible(rooms, true);
        }

        // 点击悔棋
        huiqi.onclick = () => {
            if (!isStart) {
                return;
            }
            if (!confirm('您确定要悔棋吗？')) {
                return;
            }
            send({
                type: 'canback'
            });
            waiting = true;
            alert('请求已发送，请等待对方回应');
        }

        // 加入房间
        const joinRoom = (msg) => {
            if (msg.msg) {
                return alert(msg.msg);
            }
            msgs.innerText = '';
            visible(rooms);
            visible(canvasPanel, true);
            document.all.title.innerText = room.name;
            document.all.self.innerText = nickName;
            qizi = copyQizi();
            draw();
        }

        // 初始化状态
        const init = () => {
            waiting = false;
            drawing = false;
        }

        // 开始
        const start = (msg) => {
            try {
                init();
                isStart = true;
                msgs.innerText = '';
                setTrun(msg.trun);
                document.all.self.innerText = nickName;
                document.all.opponent.innerText = msg.opponent.name;
                document.all.title.innerText = msg.room.name;
                qizi = copyQizi();  //复制棋子
                qizi.forEach(item => {
                    // 如果拿的是红棋，则将红棋的位子放到这边
                    if (msg.red) {
                        item.self = item.color === 'red';
                        // 把位置重新计算
                        if (item.color === 'red') {
                            item.y = 9 - item.y;
                        } else {
                            item.y = Math.abs(item.y - 9);
                        }
                    } else {
                        item.self = item.color === 'black';
                    }
                });
                visible(rooms);
                visible(canvasPanel, true);
                draw();
            } catch (error) { }
        }

        // 清空消息
        const clearMsg = () => {
            msgs.innerHTML = '';
        }

        // 添加消息
        const addMsg = (msg) => {
            msgs.innerHTML += `${msg}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${new Date().toLocaleString()} <br />`;
            msgs.scrollTop = Number.MAX_SAFE_INTEGER;
        }

        // 双击房间
        rooms.ondblclick = (e) => {
            let el = e.target || e.srcElement;
            if (el.tagName !== 'DIV') {
                el = el.parentNode;
            }
            const rid = el.getAttribute('rid');
            if (!rid) {
                return;
            }
            room = roomList.find(c => c.id == rid);
            if (!room) {
                return;
            }
            if (!confirm(`您确定要加入${room.name}吗？`)) {
                return;
            }
            send({
                type: 'join',
                id: room.id
            });
        }

        // 登录
        const login = (msg) => {
            visible(loginPanel);
            visible(canvasPanel);
            visible(rooms, true);
            uid = msg.uid;
            send({ type: 'rooms' });
        }

        // 收到消息
        const onmessage = ({ data }) => {
            try {
                const msg = JSON.parse(data);
                switch (msg.type) {
                    case 'login':
                        return login(msg);
                    case 'rooms':
                        return createRooms(msg.rooms);
                    case 'join':
                        return joinRoom(msg);
                    case 'start':
                        return start(msg);
                    case 'go':
                        return go(msg);
                    case 'exit':
                        return exit();
                    case 'canback':
                        return askBack();
                    case 'backresult':
                        return back(msg);
                }
            } catch (error) {
                console.log(error);
            }
        }

        // 询问反悔
        const askBack = () => {
            if (confirm('对方想悔棋，您是否同意？')) {
                send({
                    type: 'back',
                    can: true
                });
            } else {
                send({
                    type: 'back',
                    can: false
                });
            }
        }

        // 根据ID找棋子
        const findQiziById = (id) => qizi.find(c => c.id === id);

        // 找到原始棋子
        const findOriginQiziById = (id) => {
            const qizi = originQizi.find(c => c.id === id);
            return qizi ? { ...qizi } : undefined;
        };

        // 处理历史记录
        const handleHistory = (history) => {
            clearMsg();
            const self = history.length ? history[0].uid === uid : trun;    //是否是自己的回合
            if (self) {
                addMsg('本次是你的回合，请走棋');
            } else {
                //如果本次是本人下的则是对方走棋
                addMsg('本次是对方的回合，请等待对方走棋');
            }
            for (const item of history) {
                const kill = findOriginQiziById(item.kill);
                const qizi = findOriginQiziById(item.qizi);
                const self = item.uid === uid ? '您' : '对方', opponent = item.uid === uid ? '对方' : '您';
                if (kill) {
                    addMsg(`${self}的${qizi?.name}吃了${opponent}的${kill.name}`);
                    if (kill.type === 'jiang') {
                        isStart = false;
                        return addMsg(`${self}赢了`);
                    }
                } else {
                    addMsg(`${self}走了${qizi?.name}`);
                }
                if (item.uid !== uid) {
                    // 如果不是本人下的则是本人走棋
                    addMsg('本次是你的回合，请走棋');
                } else {
                    //如果本次是本人下的则是对方走棋
                    addMsg('本次是对方的回合，请等待对方走棋');
                }
            }
        }

        // 悔棋结果
        const back = (msg) => {
            waiting = false;
            if (msg.msg) {
                return alert(msg.msg);
            }
            const q = findQiziById(msg.qizi); //上次走的棋
            const kill = findOriginQiziById(msg.kill);  //吃的棋
            let { xy: { x, y }, from: { x: x1, y: y1 }, uid: id } = msg;
            trun = id === uid;  //看上次是谁下的棋
            // 如果存在被吃的棋则还原
            if (kill) {
                Object.assign(kill, { x, y: trun ? y : 9 - y });
                const sq = qizi.find(c => c.color === kill.color);  //找相同颜色的棋子
                kill.self = sq.self;
                qizi.push(kill);    //将被吃的棋还原
            }
            // 如果是对手下的棋则反向计算y
            if (!trun) {
                y1 = 9 - y1;
            }
            handleHistory(msg.history);
            selectedItem = q;
            drawAnimate(q, { x: x1, y: y1 });
        }

        // 走棋
        const go = (msg) => {
            try {
                //先走棋
                const q = findQiziById(msg.qizi);
                if (!q) {
                    return;
                }
                let dest = {};
                if (msg.kill) {
                    // 如果是吃棋
                    dest = findQiziById(msg.kill);
                } else {
                    const { xy: { x, y }, self } = msg;
                    dest.x = x;
                    //如果是自己走棋不用计算，如果是对方走棋则需倒着计算
                    dest.y = self ? y : (9 - y);
                }
                handleHistory(msg.history);
                drawingId = q.id;
                drawAnimate(q, dest);
                // 已产生结果
                if (msg.result) {
                    trun = false;
                    return;
                }
                trun = msg.trun;
            } catch (error) {
                console.log(error);
            } finally {
                draw();
            }
        }

        // 对手退出
        const exit = () => {
            try {
                isStart = false;
                document.all.opponent.innerText = '';
                alert('对手已退出游戏');
            } catch (error) { }
        }

        // 发送消息
        const send = (msg) => {
            try {
                ws && ws.send(JSON.stringify(msg));
            } catch (error) {
            }
        }

        // 点击登录按钮
        btnLogin.onclick = () => {
            nickName = document.all.name.value.trim();
            if (!nickName) {
                return alert('昵称不能为空');
            }
            const url = document.all.url.value.trim();
            if (!url) {
                return alert('链接不能为空');
            }
            ws = new WebSocket(`${url}?name=${nickName}`);
            ws.onclose = () => {
                visible(loginPanel, true);
                visible(canvasPanel);
                visible(rooms);
            }
            ws.onmessage = onmessage;
        }

        const lines = 10,    //行数
            cols = 9; //列数
        const wh = 60;  //象棋宽高
        const cellWh = wh + 10; //格子宽高
        const width = cellWh * (cols - 1);
        const height = cellWh * (lines - 1);
        const x = wh,
            y = wh,
            right = x + width,
            bottom = y + height;

        canvas.width = width + cellWh * 2;
        msgs.style.height = msgs.style.maxHeight = (canvas.height = height + cellWh * 2) + 'px';
        const c = canvas.getContext('2d');

        //下棋区域的宽高  
        const panWidth = cellWh * (cols - 1),
            panHeight = cellWh * ((lines - 2) / 2);

        // 根据xy坐标计算位置
        const calcXY = (_x, _y) => [x + _x * cellWh, y + _y * cellWh];
        // 根据xy坐标计算矩形盒子
        const calcRect = (_x, _y) => {
            const [x, y] = calcXY(_x, _y);  //拿到中心位置
            const half = wh / 2;    //象棋一半的宽高
            return {
                x: x - half,
                y: y - half,
                r: x - half + wh,
                b: y - half + wh
            };
        };
        // 验证坐标是否有交集
        const interest = (_x, _y, x1, y1) => {
            const r = calcRect(_x, _y);
            return x1 >= r.x && x1 <= r.r && y1 >= r.y && y1 <= r.b;
        }
        // 根据xy查找坐标系
        const findXY = (_x, _y) => {
            for (let l = 0; l < lines; l++) {
                for (let c = 0; c < cols; c++) {
                    if (interest(c, l, _x, _y)) {
                        return [c, l];
                    }
                }
            }
            return [-1, -1];
        }

        // 初始化的棋子
        const originQizi = [{
            x: 0,
            y: 0,
            self: false,
            name: '车',
            color: 'red',
            type: 'ju',
        },
        {
            x: 1,
            y: 0,
            self: false,
            name: '马',
            color: 'red',
            type: 'ma',
        },
        {
            x: 2,
            y: 0,
            self: false,
            name: '相',
            color: 'red',
            type: 'xiang',
        },
        {
            x: 3,
            y: 0,
            self: false,
            name: '士',
            color: 'red',
            type: 'shi',
        },
        {
            x: 4,
            y: 0,
            self: false,
            name: '帅',
            color: 'red',
            type: 'jiang',
        },
        {
            x: 5,
            y: 0,
            self: false,
            name: '士',
            color: 'red',
            type: 'shi',
        },
        {
            x: 6,
            y: 0,
            self: false,
            name: '相',
            color: 'red',
            type: 'xiang',
        },
        {
            x: 7,
            y: 0,
            self: false,
            name: '马',
            color: 'red',
            type: 'ma',
        },
        {
            x: 8,
            y: 0,
            self: false,
            name: '车',
            color: 'red',
            type: 'ju',
        },
        {
            x: 1,
            y: 2,
            self: false,
            name: '炮',
            color: 'red',
            type: 'pao',
        },
        {
            x: 7,
            y: 2,
            self: false,
            name: '炮',
            color: 'red',
            type: 'pao',
        },
        {
            x: 0,
            y: 3,
            self: false,
            name: '兵',
            color: 'red',
            type: 'bing',
        },
        {
            x: 2,
            y: 3,
            self: false,
            name: '兵',
            color: 'red',
            type: 'bing',
        },
        {
            x: 4,
            y: 3,
            self: false,
            name: '兵',
            color: 'red',
            type: 'bing',
        },
        {
            x: 6,
            y: 3,
            self: false,
            name: '兵',
            color: 'red',
            type: 'bing',
        },
        {
            x: 8,
            y: 3,
            self: false,
            name: '兵',
            color: 'red',
            type: 'bing',
        },
        {
            x: 0,
            y: 9,
            self: true,
            name: '車',
            color: 'black',
            type: 'ju',
        },
        {
            x: 1,
            y: 9,
            self: true,
            name: '馬',
            color: 'black',
            type: 'ma',
        },
        {
            x: 2,
            y: 9,
            self: true,
            name: '象',
            color: 'black',
            type: 'xiang',
        },
        {
            x: 3,
            y: 9,
            self: true,
            name: '仕',
            color: 'black',
            type: 'shi',
        },
        {
            x: 4,
            y: 9,
            name: '将',
            self: true,
            color: 'black',
            type: 'jiang',
        },
        {
            x: 5,
            y: 9,
            self: true,
            name: '仕',
            color: 'black',
            type: 'shi',
        },
        {
            x: 6,
            y: 9,
            self: true,
            name: '象',
            color: 'black',
            type: 'xiang',
        },
        {
            x: 7,
            y: 9,
            self: true,
            name: '馬',
            color: 'black',
            type: 'ma',
        },
        {
            x: 8,
            y: 9,
            self: true,
            name: '車',
            color: 'black',
            type: 'ju',
        },
        {
            x: 1,
            y: 7,
            self: true,
            name: '炮',
            color: 'black',
            type: 'pao',
        },
        {
            x: 7,
            y: 7,
            self: true,
            name: '炮',
            color: 'black',
            type: 'pao',
        },
        {
            x: 0,
            y: 6,
            self: true,
            name: '卒',
            color: 'black',
            type: 'bing',
        },
        {
            x: 2,
            y: 6,
            self: true,
            name: '卒',
            color: 'black',
            type: 'bing',
        },
        {
            x: 4,
            y: 6,
            self: true,
            name: '卒',
            color: 'black',
            type: 'bing',
        },
        {
            x: 6,
            y: 6,
            self: true,
            name: '卒',
            color: 'black',
            type: 'bing',
        },
        {
            x: 8,
            y: 6,
            self: true,
            name: '卒',
            color: 'black',
            type: 'bing',
        }
        ];

        // 添加唯一ID
        originQizi.forEach((item, index) => item.id = index + 1);

        // 复制棋子
        const copyQizi = () => originQizi.map(c => ({ ...c }));

        // 棋子
        let qizi = copyQizi();

        // 选中的棋子
        let selectedItem = {};

        // 绘制的棋子ID
        let drawingId = 0;

        // 绘制
        function draw(noDrawSelected = false) {
            c.clearRect(0, 0, width + cellWh * 2, height + cellWh * 2);
            c.beginPath();
            c.font = `bold ${wh - 15}px 楷体`;
            c.textBaseline = 'middle';
            c.textAlign = 'center';
            c.strokeStyle = 'black';
            c.fillStyle = 'black';
            // 绘制中间文字和下棋区域
            c.fillText('楚河', x + panWidth / 2 / 2, y + panHeight + (cellWh / 2));
            c.fillText('汉界', x + panWidth - panWidth / 2 / 2, y + panHeight + (cellWh / 2));
            // 绘制横线
            for (let index = 0; index < lines; index++) {
                c.moveTo(x, y + index * cellWh);
                c.lineTo(right, y + index * cellWh);
            }

            // 绘制竖线
            for (let index = 0; index < cols; index++) {
                c.moveTo(x + index * cellWh, y);
                c.lineTo(x + index * cellWh, panHeight + y);
            }

            for (let index = 0; index < cols; index++) {
                c.moveTo(x + index * cellWh, y + panHeight + cellWh);
                c.lineTo(x + index * cellWh, bottom);
            }
            c.moveTo(x, y + panHeight);
            c.lineTo(x, y + panHeight + cellWh);
            c.moveTo(right, y + panHeight);
            c.lineTo(right, y + panHeight + cellWh);
            // 绘制将帅的交叉线
            const rectLines = [{ x: 3, y: 0, x1: 5, y1: 2 }, { x: 3, y: 2, x1: 5, y1: 0 },
            { x: 3, y: 9, x1: 5, y1: 7 }, { x: 3, y: 7, x1: 5, y1: 9 }];
            for (const item of rectLines) {
                let [x, y] = calcXY(item.x, item.y);
                c.moveTo(x, y);
                [x, y] = calcXY(item.x1, item.y1);
                c.lineTo(x, y);
            }
            c.stroke();
            // 绘制象棋
            c.textAlign = 'center';
            c.textBaseline = 'middle';
            c.lineWidth = 2;
            for (const item of qizi) {
                if ((item === selectedItem || item.id === drawingId) && noDrawSelected) {
                    continue;
                }
                drawQizi(item);
            }
        }

        // 绘制棋子
        const drawQizi = (item, xy) => {
            c.beginPath();
            let [x, y] = calcXY(item.x, item.y);
            if (xy) {
                x = xy.x;
                y = xy.y;
            }
            c.arc(x, y, cellWh / 2 - 5, 0, 2 * Math.PI);    //先将底部绘制白色
            c.fillStyle = 'white';
            c.fill();
            c.closePath();
            c.beginPath();
            c.save();
            if (selectedItem === item) {
                c.shadowOffsetX = 10;
                c.shadowOffsetY = 5;
                c.shadowColor = 'rgba(0,0,0,0.3)';
            }
            c.arc(x, y, cellWh / 2 - 8, 0, 2 * Math.PI);    //绘制棋子的底色
            c.fillStyle = item.color;
            c.fill();
            c.closePath();
            c.restore();
            c.beginPath();
            c.strokeStyle = selectedItem === item ? 'red' : 'black';
            c.arc(x, y, cellWh / 2 - 5, 0, 2 * Math.PI);    //绘制象棋的外圈
            c.stroke();
            c.fillStyle = 'white';
            c.fillText(item.name, x, y - 1);    //绘制象棋的文字
            c.stroke();
            c.closePath();
        }

        // 绘制动画
        function drawAnimate(qizi, dest) {
            drawing = true;
            let [x, y] = calcXY(qizi.x, qizi.y);    //要绘制的棋子位置
            let [x1, y1] = calcXY(dest.x, dest.y);   //要走的位置

            let disx = x1 - x,
                disy = y1 - y,
                ratio = 11, //速率
                max = Math.max(Math.abs(disx), Math.abs(disy)); //最大值
            disx /= max;    //x差值
            disy /= max;    //y差值

            const animate = () => {
                draw(true);
                x = Math[disx >= 0 ? 'min' : 'max'](x + disx * ratio, x1);   //x的下一步位置
                y = Math[disy >= 0 ? 'min' : 'max'](y + disy * ratio, y1);   //y的下一步位置
                drawQizi(qizi, { x, y });
                if (x === x1 && y === y1) {
                    dest.type && deleteQizi(dest);  //如果走的是棋子则吃掉
                    Object.assign(qizi, { x: dest.x, y: dest.y });
                    selectedItem = {};
                    drawingId = 0;
                    drawing = false;
                    return draw();
                }
                requestAnimationFrame(animate);
            }
            animate();
        }

        // 根据xy查找棋子
        const findQizi = (x, y) => qizi.find(c => c.x === x && c.y === y);
        // 删除棋子
        const deleteQizi = (item) => item && qizi.splice(qizi.indexOf(item), 1);
        // 根据XY删除棋子
        const deleteQiziByXY = (x, y) => {
            const q = findQizi(x, y);
            deleteQizi(q);
        }
        // 根据位置找棋子
        const findQiziByLocation = (offsetX, offsetY) => {
            const [x, y] = findXY(offsetX, offsetY);
            if (x > -1) {
                return findQizi(x, y);
            }
        }
        // 验证范围是否有效
        const validRange = (x, y) => {
            return x >= 0 && x < cols && y >= 0 && y < lines;
        }

        // 马
        const ma = (item, x, y) => {
            x = x - item.x;
            y = y - item.y;
            let l = Math.abs(x);
            let t = Math.abs(y);
            if ((l === 1 && t === 2) || (l === 2 && t === 1)) {
                // 如果OK看看是否撇马脚
                l = item.x + (l === 1 ? 0 : x / 2);
                t = item.y + (t === 1 ? 0 : y / 2);
                return !findQizi(l, t);
            }
            return false;
        }

        // 相
        const xiang = (item, x, y) => {
            // 不能跳过河
            if (y < 5) {
                return false;
            }
            let l = Math.abs(x - item.x);
            let t = Math.abs(y - item.y);
            if (l !== 2 || t !== 2) {
                return false;
            }
            l = item.x + (x - item.x) / 2;
            t = item.y + (y - item.y) / 2;
            return !findQizi(l, t);
        }

        // 兵
        const bing = (item, x, y) => {
            // 不能后退
            if (y > item.y) {
                return false;
            }
            // 每次只能走一步
            if (Math.abs(y - item.y) > 1) {
                return false;
            }
            // 在自己的地盘上不能左右走
            if (y > 4 && x !== item.x) {
                return false;
            }
            return true;
        }

        // 士
        const shi = (item, x, y) => {
            if (y < 7 || x < 3 || x > 5) {
                return false;
            }
            let l = Math.abs(x - item.x);
            let t = Math.abs(y - item.y);
            return l === 1 && t === 1;
        }

        // 将
        const jiang = (item, x, y) => {
            // 两种情况，一种是吃将，一种是是范围内活动
            const qizi = findQizi(x, y);
            // 如果是吃将
            if (qizi?.type === 'jiang') {
                // 如果吃将是横向走则无效
                if (item.x !== x) {
                    return false;
                }
                let t = y + 1;
                // 查找中间有没有棋子
                while (t < item.y) {
                    if (findQizi(x, t)) {
                        return false;
                    }
                    t++;
                }
                return true;
            } else {
                // 在特定范围内活动
                if (y < 7 || x < 3 || x > 5) {
                    return false;
                }
                let l = Math.abs(x - item.x);
                let t = Math.abs(y - item.y);
                return (l === 1 && t === 0) || (l === 0 && t === 1)
            }
        }

        // 炮
        const pao = (item, x, y) => {
            let l = x - item.x;
            let t = y - item.y;
            if (l !== 0 && t !== 0) {
                return false;
            }
            // 只能直线行走
            // 计算中间有几个棋子
            let x1 = x, x2 = x, y1 = y, y2 = y, count = 0;
            if (l === 0) {
                // 上下走
                if (t > 0) {
                    // 往下走
                    y1 = item.y + 1;
                    y2 = y;
                } else {
                    y1 = y + 1;
                    y2 = item.y;
                }
                while (y1 < y2) {
                    if (findQizi(x1, y1)) {
                        count++;
                    }
                    y1++;
                }
            } else if (t === 0) {
                // 左右走
                if (l > 0) {
                    // 往右走
                    x1 = item.x + 1;
                    x2 = x;
                } else {
                    x1 = x + 1;
                    x2 = item.x;
                }
                while (x1 < x2) {
                    if (findQizi(x1, y1)) {
                        count++;
                    }
                    x1++;
                }
            }
            const destObj = findQizi(x, y);  //查找目标对象
            // 如果是目标对象并且路径只有一个对象则有效
            if (destObj && count === 1) {
                return true;
            }
            if (!destObj && count === 0) {
                return true;
            }
            return false;
        }

        // 车
        const ju = (item, x, y) => {
            let l = x - item.x;
            let t = y - item.y;
            if (l !== 0 && t !== 0) {
                return false;
            }
            // 只能直线行走
            // 计算中间有几个棋子
            let x1 = x, x2 = x, y1 = y, y2 = y, count = 0;
            if (l === 0) {
                // 上下走
                if (t > 0) {
                    // 往下走
                    y1 = item.y + 1;
                    y2 = y;
                } else {
                    y1 = y + 1;
                    y2 = item.y;
                }
                while (y1 < y2) {
                    if (findQizi(x1, y1)) {
                        count++;
                    }
                    y1++;
                }
            } else if (t === 0) {
                // 左右走
                if (l > 0) {
                    // 往右走
                    x1 = item.x + 1;
                    x2 = x;
                } else {
                    x1 = x + 1;
                    x2 = item.x;
                }
                while (x1 < x2) {
                    if (findQizi(x1, y1)) {
                        count++;
                    }
                    x1++;
                }
            }
            return count === 0; //路径上不能有棋子
        }

        // 校验
        const valid = (item, x, y) => {
            if (!validRange(x, y)) {
                return false;
            }
            switch (item.type) {
                case 'ma':
                    return ma(item, x, y);
                case 'xiang':
                    return xiang(item, x, y);
                case 'bing':
                    return bing(item, x, y);
                case 'shi':
                    return shi(item, x, y);
                case 'jiang':
                    return jiang(item, x, y);
                case 'pao':
                    return pao(item, x, y);
                case 'ju':
                    return ju(item, x, y);
                default:
                    return false;
            }
        }

        // 点击事件
        canvas.onclick = (e) => {
            if (drawing || waiting) {
                return;
            }
            const { offsetX, offsetY } = e;
            try {
                // 如果刚才已选中
                if (selectedItem.type) {
                    let [x, y] = findXY(offsetX, offsetY);
                    if (x < -1) {   //如果找不到坐标则取消选项
                        selectedItem = {};
                        return;
                    }
                    const item = findQizi(x, y);  //先查找点击的是否为棋子
                    // 如果点击的是本身则取消选中状态
                    if (item === selectedItem) {
                        selectedItem = {};
                        return;
                    }
                    // 如果选中的自己的棋子则切换选中的棋子
                    if (item?.self === selectedItem.self) {
                        selectedItem = item;
                        return;
                    }
                    // 如果不是自己的回合
                    if (!trun) {
                        // 如果选中的不是棋子
                        if (!item) {
                            selectedItem = {};
                        }
                        return;
                    }
                    // 如果没有选中棋子则看是否有坐标，有坐标则跳转
                    // 如果选中了棋子则吃掉
                    if (!valid(selectedItem, x, y)) {
                        return;
                    }
                    send({
                        type: 'go',
                        qizi: selectedItem.id,
                        xy: { x, y },
                        from: { x: selectedItem.x, y: selectedItem.y },
                        code: item?.type,
                        kill: item?.id
                    });
                } else {
                    // 如果没选中则查看是否有能选中的棋子
                    for (const item of qizi) {
                        if (interest(item.x, item.y, offsetX, offsetY)) {
                            item.self && (selectedItem = item);    //如果有交集的坐标则选中象棋
                            break;
                        }
                    }
                }
            } catch (error) {
                console.log(error);
            } finally {
                draw();
            }
        }

        draw();
    </script>
</body>

</html>